package com.kantboot.system.user.service.impl;

import com.kantboot.system.user.dao.repository.entity.SysBalanceChangeRecordRepository;
import com.kantboot.system.user.dao.repository.entity.SysUserBalanceRepository;
import com.kantboot.system.user.domain.dto.BalanceChangeDTO;
import com.kantboot.system.user.domain.entity.SysBalance;
import com.kantboot.system.user.domain.entity.SysBalanceChangeRecord;
import com.kantboot.system.user.domain.entity.SysUserBalance;
import com.kantboot.system.user.exception.BalanceNotEnoughException;
import com.kantboot.system.user.exception.SystemUserException;
import com.kantboot.system.user.exception.UserBalanceLockException;
import com.kantboot.system.user.service.ISysBalanceService;
import com.kantboot.system.user.service.ISysUserBalanceService;
import com.kantboot.system.user.service.IUserSelfService;
import com.kantboot.util.common.exception.BaseException;
import com.kantboot.util.core.redis.RedisUtil;
import jakarta.annotation.Resource;
import jakarta.transaction.Transactional;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class SysUserBalanceServiceImpl implements ISysUserBalanceService {
    // TODO 修改余额时的,宕机重启后的数据一致性问题还没有进行处理

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private SysUserBalanceRepository repository;

    @Resource
    private ISysBalanceService balanceService;

    @Resource
    private SysBalanceChangeRecordRepository balanceChangeRecordRepository;

    @Resource
    private ApplicationContext applicationContext;

    @Resource
    private IUserSelfService userSelfService;


    /**
//     * 定时器，每30秒一次
//     */
//    @Scheduled(cron = "0 0/1 * * * ?")
//    public void changeBalanceInnerRetryScheduled() {
////         BusWaterBalanceCodeMenu.BALANCE_CODE_MONTH_CONSUMPTION
//        // 根据余额编码查询;
//        // 如果时间是每月1号，且时间是0点，且分钟是0
//        // 获取当前时间
//        Date date = new Date();
//        // 获取日
//        int day = date.getDate();
//        // 获取小时
//        int hours = date.getHours();
//        // 获取分钟
//        int minutes = date.getMinutes();
////
////        if (day == 1 && hours == 0 && minutes == 0) {
//////            List<SysUserBalance> byBalanceCode = repository.findByBalanceCode(BusWaterBalanceCodeMenu.BALANCE_CODE_MONTH_CONSUMPTION);
//////            for (SysUserBalance sysUserBalance : byBalanceCode) {
//////                // 清空月消费余额
//////                sysUserBalance.setNumber(BigDecimal.ZERO);
//////            }
////            repository.saveAll(byBalanceCode);
////        }
//    }


    /**
     * 根据用户id增加余额
     *
     * @param dto 余额增加参数
     * @return 增加后的余额
     */
    @SneakyThrows
    @Override
    public SysBalanceChangeRecord changeBalance(BalanceChangeDTO dto) {
        // 用户id
        Long userId = dto.getUserId();
        // 余额类型
        String balanceCode = dto.getBalanceCode();
        // 数量
        BigDecimal number = dto.getNumber();
        // 余额变化原因编码
        String reasonCode = dto.getReasonCode();
        log.info("用户id:{},余额类型:{},数量:{},原因编码:{}", userId, balanceCode, number, reasonCode);
        // 如果number小于0
        if (number.doubleValue() < 0) {
            Long numberByUserIdAndBalanceCode = repository.findNumberByUserIdAndBalanceCode(userId, balanceCode);
            if (numberByUserIdAndBalanceCode == null || numberByUserIdAndBalanceCode.doubleValue() < Math.abs(number.doubleValue())) {
                // 如果余额不存在
                throw new BalanceNotEnoughException();
            }

        }

        // 查询余额状态
        String statusCodeByUserIdAndBalanceCode = repository.findStatusCodeByUserIdAndBalanceCode(userId, balanceCode);
        if (SysUserBalance.STATUS_CODE_LOCK.equals(statusCodeByUserIdAndBalanceCode)) {
            // 余额被锁定的情况下
            throw new UserBalanceLockException();
        }

        SysBalanceChangeRecord save = balanceChangeRecordRepository.save(
                new SysBalanceChangeRecord()
                        .setUserId(userId)
                        .setBalanceCode(balanceCode)
                        .setNumber(number)
                        .setReasonCode(reasonCode)
                        .setStatusCode(SysBalanceChangeRecord.STATUS_CODE_NOT_PROCESSED)
        );

//        BalanceChangeEvent event = new BalanceChangeEvent(this, save);
//        // 发布事件
//        applicationContext.publishEvent(event);
        // 直接修改余额，不进行事件发布
        // 修改余额
        SysUserBalance byUserIdAndBalanceCode = repository.findByUserIdAndBalanceCode(userId, balanceCode);
        if (byUserIdAndBalanceCode == null) {
            repository.save(
                    new SysUserBalance()
                            .setUserId(userId)
                            .setBalanceCode(balanceCode)
                            .setNumber(BigDecimal.ZERO)
                            .setStatusCode(SysUserBalance.STATUS_CODE_NORMAL)
            );
            repository.flush();
        }
        repository.addBalance(balanceCode, userId, number);
        // 修改余额变化记录状态
        return balanceChangeRecordRepository.findById(save.getId()).orElse(null);
    }

    /**
     * 该方法在发布的事件中调用,在BalanceChangeListener中调用
     *
     * @param record 余额变化记录
     * @param isRetry 是否是重试
     * @throws BaseException 余额变化异常
     */
    @Modifying
    @Transactional
    @Override
    public void changeBalanceInner(SysBalanceChangeRecord record, boolean isRetry) {

        // 开始时间
        long startTime = System.currentTimeMillis();

        // 用户id
        Long userId = record.getUserId();
        // 余额类型
        String balanceCode = record.getBalanceCode();
        // 数量
        BigDecimal number = record.getNumber();

        // 加锁
        String redisLockKey = "lock:sysUserBalance:addBalance:" + userId + ":" + balanceCode;
        if (!redisUtil.lock(redisLockKey, 1, TimeUnit.MINUTES)) {
            balanceChangeRecordRepository.updateStatusCodeById(
                    record.getId(), SysBalanceChangeRecord.STATUS_CODE_PROCESSING);
            if (isRetry) {
                changeBalanceInnerRetry(record);
            }
            throw SystemUserException.CHANGING_BALANCE;
        }
        Long numberByUserIdAndBalanceCode =
                repository.findNumberByUserIdAndBalanceCode(userId, balanceCode);
        if (numberByUserIdAndBalanceCode == null) {
            repository.save(
                    new SysUserBalance()
                            .setUserId(userId)
                            .setBalanceCode(balanceCode)
                            .setNumber(BigDecimal.ZERO)
                            .setStatusCode(SysUserBalance.STATUS_CODE_NORMAL)
            );
            repository.flush();
        }
        repository.addBalance(balanceCode, userId, number);
        redisUtil.unlock(redisLockKey);

        // 结束时间
        long endTime = System.currentTimeMillis();
        // 耗时
        long costTime = endTime - startTime;
        log.info("修改余额的内部操作解锁耗时：{}ms", costTime);
        // 修改余额变化记录状态
        balanceChangeRecordRepository.updateStatusCodeById(
                record.getId(), SysBalanceChangeRecord.STATUS_CODE_PROCESSED);
        // 查询余额状态
        String statusCodeByUserIdAndBalanceCode = repository.findStatusCodeByUserIdAndBalanceCode(userId, balanceCode);
        if (!SysUserBalance.STATUS_CODE_LOCK.equals(statusCodeByUserIdAndBalanceCode)) {
            // 如果余额没有被锁定
            return;
        }
        // 如果余额被锁定
        // 查询余额变化记录中是否还有未处理和被锁定的记录
        Long count = balanceChangeRecordRepository.countByUserIdAndStatusCodeIn(
                userId, SysBalanceChangeRecord.STATUS_CODE_NOT_PROCESSED, SysBalanceChangeRecord.STATUS_CODE_LOCK);
        if (count == 0) {
            // 如果没有未处理和被锁定的记录
            // 将余额状态修改为正常
            repository.updateStatusCodeByUserIdAndBalanceCode(
                    userId, balanceCode, SysUserBalance.STATUS_CODE_NORMAL);
        }

    }

    /**
     * 余额变化事件重试
     *
     * @param record 余额变化记录
     *               重试次数达到上限后，将余额变化记录状态修改为lock
     */
    @Modifying
    @Transactional
    @Override
    public void changeBalanceInnerRetry(SysBalanceChangeRecord record) {
        for (int i = 0; i < 100; i++) {
            log.info("余额变化事件重试,第{}次,记录id:{}", i + 1, record.getId());
            try {
                Thread.sleep(1000);
                changeBalanceInner(record, false);
                log.info("余额变化事件重试成功,在第{}次,记录id:{}", i + 1, record.getId());
                return;
            } catch (BaseException e) {
                log.error("余额变化事件重试失败,继续重试,记录id:{}", record.getId());
            } catch (InterruptedException e) {
                log.error("余额变化事件重试失败,继续重试,记录id:{}", record.getId());
                throw new RuntimeException(e);
            }
        }
        log.error("余额变化事件重试失败,重试次数已达上限,进行暂时封锁,记录id:{}", record.getId());
        balanceChangeRecordRepository.updateStatusCodeById(
                record.getId(), SysBalanceChangeRecord.STATUS_CODE_LOCK);
        // 封锁余额
        repository.updateStatusCodeByUserIdAndBalanceCode(
                record.getUserId(), record.getBalanceCode(), SysUserBalance.STATUS_CODE_LOCK);
        // 先将这笔记录继续进行内部调用
        changeBalanceInner(record, true);
    }

    @Override
    public SysUserBalance getBalanceByCodeAndUserId(String code, Long selfId) {
        SysUserBalance byUserIdAndBalanceCode = repository.findByUserIdAndBalanceCode(selfId, code);
        if (byUserIdAndBalanceCode == null) {
            // 如果余额不存在，则默认为0
            return new SysUserBalance()
                    .setUserId(selfId)
                    .setBalanceCode(code)
                    .setNumber(BigDecimal.ZERO)
                    .setStatusCode(SysUserBalance.STATUS_CODE_NORMAL);
        }
        return byUserIdAndBalanceCode;
    }

    @Override
    public SysUserBalance getBalanceSelfByCode(String code) {
//        return repository.findByUserIdAndBalanceCode(userSelfService.getSelfId(), code);
        return getBalanceByCodeAndUserId(code, userSelfService.getSelfId());
    }

    @Override
    public Map<String, BigDecimal> getBalanceMapByUserId(Long userId) {
        List<SysBalance> all = balanceService.getAll();
        Map<String, BigDecimal> map = new HashMap<>();
        for (SysBalance sysBalance : all) {
            map.put(sysBalance.getCode(), BigDecimal.ZERO);
        }
        List<SysUserBalance> byUserId = repository.findByUserId(userId);
        for (SysUserBalance sysUserBalance : byUserId) {
            map.put(sysUserBalance.getBalanceCode(), sysUserBalance.getNumber());
        }
        return map;
    }

    @Override
    public Map<String, BigDecimal> getBalanceSelfMap() {
        return getBalanceMapByUserId(userSelfService.getSelfId());
    }
}
